// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package macaroon

import (
	"common/macaroon/pb"
	"common/uuid"
	"crypto/md5"
	"github.com/zeebo/errs"
	"google.golang.org/protobuf/proto"

	"common/base58"
)

var (
	// Error is a general API Key error.
	Error = errs.Class("api key")
	// ErrFormat means that the structural formatting of the API Key is invalid.
	ErrFormat = errs.Class("api key format")
	// ErrInvalid means that the API Key is improperly signed.
	ErrInvalid = errs.Class("api key invalid")
	// ErrUnauthorized means that the API key does not grant the requested permission.
	ErrUnauthorized = errs.Class("api key unauthorized")
	// ErrRevoked means the API key has been revoked.
	ErrRevoked = errs.Class("api key revocation")
)

type ApiActionType int

const (
	// not using iota because these values are persisted in macaroons.
	_ ApiActionType = 0

	ApiActionRead  ApiActionType = 1
	ApiActionWrite ApiActionType = 2
	//ApiActionList ApiActionType = 3
	ApiActionDelete ApiActionType = 4
)

// APIKey implements a Macaroon-backed Storj-v3 API key.
type APIKey struct {
	mac      *Macaroon
	licenses []*pb.License
	rootKey  []byte
}

// 因为国密 sm4 需要一个 16 位的密码，所以这里使用 md5 而不是 sm3
func rootKeyToMd5(rootKey string) []byte {
	m := md5.New()
	m.Write([]byte(rootKey))
	return m.Sum(nil)
}

// ParseAPIKey parses a given api key string and returns an APIKey if the
// APIKey was correctly formatted. It does not validate the key.
func ParseAPIKey(key string) (*APIKey, error) {
	data, version, err := base58.CheckDecode(key)
	if err != nil || version != 0 {
		return nil, ErrFormat.New("invalid api key format")
	}
	mac, err := ParseMacaroon(data)
	if err != nil {
		return nil, ErrFormat.Wrap(err)
	}

	return &APIKey{mac: mac, licenses: mac.LicencesUnmark(), rootKey: rootKeyToMd5(mac.Head().String())}, nil
}

// ParseRawAPIKey parses raw api key data and returns an APIKey if the APIKey
// was correctly formatted. It does not validate the key.
func ParseRawAPIKey(data []byte) (*APIKey, error) {
	mac, err := ParseMacaroon(data)
	if err != nil {
		return nil, ErrFormat.Wrap(err)
	}
	return &APIKey{mac: mac, licenses: mac.LicencesUnmark(), rootKey: rootKeyToMd5(mac.Head().String())}, nil
}

// NewAPIKey generates a brand new unrestricted API key given the provided.
// server project secret.
func NewAPIKey(userId uuid.UUID, secret []byte) (*APIKey, error) {
	if userId.IsZero() {
		return nil, Error.New("userId is error")
	}
	mac, err := NewUnrestricted(userId, secret)
	if err != nil {
		return nil, Error.Wrap(err)
	}
	return &APIKey{mac: mac, rootKey: rootKeyToMd5(mac.Head().String())}, nil
}

// Head returns the identifier for this macaroon's root ancestor.
func (a *APIKey) Head() uuid.UUID {
	return a.mac.Head()
}

func (a *APIKey) KeyId() string {
	return a.Head().String()
}

// Tail returns the identifier for this macaroon only.
func (a *APIKey) Tail() []byte {
	return a.mac.Tail()
}

// Serialize serializes the API Key to a string.
func (a *APIKey) Serialize() string {
	return base58.CheckEncode(a.mac.Serialize(), 0)
}

// SerializeRaw serialize the API Key to raw bytes.
func (a *APIKey) SerializeRaw() []byte {
	return a.mac.Serialize()
}

func (a *APIKey) CheckAllow(bucket string, prefix string, action ApiActionType) bool {
	if len(a.licenses) == 0 {
		return true //这是一个特例
	}
	for _, l := range a.licenses {
		if l.Allow(bucket, prefix, pb.ActionType(action)) {
			return true
		}
	}
	return false
}

// GetLicences 调用GetLiences之前先检查签名 Validate
func (a *APIKey) GetLicences() (bool, []*pb.License) {
	licenses := make([]*pb.License, 0)
	licensesData := a.mac.Licences()
	if len(licensesData) == 0 {
		return true, licenses
	}

	for _, data := range licensesData {
		l := pb.License{}
		if proto.Unmarshal(data, &l) == nil {
			licenses = append(licenses, &l)
		}
	}
	return false, licenses
}

func (a *APIKey) AddLicense(l *pb.License) error {
	data, err := proto.Marshal(l)
	if err != nil {
		return err
	}
	a.licenses = append(a.licenses, l)
	a.mac, err = a.mac.AddFirstPartyLicence(data)
	if err != nil {
		return err
	}
	return nil
}

func (a *APIKey) Validate(secret []byte) (ok bool) {
	return a.mac.Validate(secret)
}

func (a *APIKey) RootKey() []byte {
	return a.rootKey
}
